/*
 * memory.h
 *
 * Copyright (C) 2019 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _MEMORY_H_
#define _MEMORY_H_

#include <lock.h>
#include <sdk/list.h>
#include <sdk/memory.h>
#include <sdk/avltree.h>
#include <exception.h>
#include <object.h>
#include <filesystem.h>

#define INVALID_PAGE              ((physical_t)-1)
#define PAGE_SIZE                 0x1000
#define MAX_PAGING_LEVELS         6
#define MIN_PHYS_ADDR_BITS        16
#define MAX_PHYS_ADDR_BITS        52
#define MEMORY_MAX_BLOCKS         0x80000
#define MEMORY_METADATA_TOP       0xFF000000

#define PAGE_ALIGN(x)    ((x) & ~(PAGE_SIZE - 1))
#define PAGE_OFFSET(x)   ((x) & (PAGE_SIZE - 1))
#define PAGE_NUMBER(x)   (PAGE_ALIGN(x) >> 12)
#define PAGE_ALIGN_UP(x) PAGE_ALIGN((x) + PAGE_SIZE - 1)

typedef qword_t physical_t;
typedef physical_t page_num_t;
typedef void *page_table_t;

typedef enum
{
    PAGE_STATUS_ABANDONED = 0,
    PAGE_STATUS_FREE,
    PAGE_STATUS_ALLOCATED,
    PAGE_STATUS_RESERVED,
} page_status_t;

typedef struct
{
    mini_list_entry_t stack_link;
    page_status_t status : 2;
    byte_t map_level : 3;
    page_num_t number : 40;
    size_t map_count;
} page_t;

_Static_assert(PAGE_SIZE % sizeof(page_t) == 0, "The size of page_t must be a divisor of the page size.");

typedef struct
{
    page_t *pages;
    page_num_t count;
} area_t;

typedef struct
{
    physical_t address;
    qword_t length;
    page_status_t status;
} memory_map_entry_t;

typedef enum
{
    MEMORY_ZERO_BACKED_SECTION,
    MEMORY_FILE_BACKED_SECTION,
    MEMORY_PHYSICAL_BACKED_SECTION,
} memory_section_backing_t;

typedef struct
{
    object_t header;
    memory_section_backing_t backing;
    page_num_t num_pages;

    union
    {
        page_t *pages;
        file_instance_t *file; /* strong reference */
    };
} memory_section_t;

typedef struct
{
    avl_node_t by_addr_node;
    avl_node_t by_size_node;
    uintptr_t address;
    size_t size;
    memory_flags_t flags;
    memory_section_t *section;
    page_num_t section_offset;
} memory_block_t;

typedef struct
{
    memory_block_t *blocks;
    dword_t *block_bitmap;
    avl_tree_t by_addr_tree;
    avl_tree_t by_size_tree;
    lock_t lock;
    page_t *root_page_table;
} address_space_t;

extern uintptr_t memory_metadata_base;
extern const page_table_t memory_default_table;
extern const page_table_t memory_shadow_table;
extern address_space_t *memory_lower_space;
extern address_space_t *const memory_upper_space;

void memory_init(uintptr_t mboot_tags, size_t mboot_size);
void *memory_request_metadata_space(size_t count, size_t size);

page_t *memory_acquire_page(byte_t min_bits, byte_t max_bits, size_t alignment);
void memory_acquire_area(byte_t min_bits, byte_t max_bits, size_t size, size_t alignment, area_t *area);
void memory_release_page(page_t *page);
void memory_release_area(const area_t *area);
page_t *memory_find_page_by_address(physical_t address);
void memory_claim_physical_region(physical_t address, qword_t size, page_status_t initial_status);
void memory_abandon_physical_region(physical_t address, qword_t size);
void memory_init_physical(memory_map_entry_t *mmap, size_t entry_count);

static inline bool_t is_area_valid(const area_t *area)
{
    return area->pages && area->count > 0 ? TRUE : FALSE;
}

page_t *memory_get_page_mapping(page_table_t table, void *address);
sysret_t memory_map_page(page_table_t table, page_t *page, void *address, memory_flags_t access_flags);
sysret_t memory_map_area(page_table_t table, const area_t *area, void *address, memory_flags_t access_flags);
sysret_t memory_query_page_flags(page_table_t table, void *address, memory_flags_t *access_flags);
sysret_t memory_adjust_page_flags(page_table_t table, void *address, memory_flags_t access_flags);
sysret_t memory_unmap_clear_page(page_table_t table, void *address);
sysret_t memory_unmap_keep_page(page_table_t table, void *address);
sysret_t memory_unmap_clear_area(page_table_t table, void *address, size_t size);
sysret_t memory_unmap_keep_area(page_table_t table, void *address, size_t size);
sysret_t memory_load_default_table(page_t *new_default_table);
sysret_t memory_load_shadow_table(page_t *new_shadow_table);
sysret_t memory_unload_shadow_table(void);
page_t *memory_create_page_table(void);
sysret_t memory_commit(void *address, size_t size);
void memory_init_mapping(void);

memory_block_t *memory_get_block_for_address(void *address);
sysret_t memory_allocate(address_space_t *space, void **address, size_t size, memory_flags_t flags, memory_section_t *section, page_num_t section_offset);
sysret_t memory_free(address_space_t *space, void *address);
sysret_t memory_view_area(address_space_t *space, void **address, const area_t *area, memory_flags_t flags);
sysret_t memory_pin_buffer(const void *virtual, void **pinned, size_t size, bool_t lock_contents);
void memory_init_virtual(const area_t *kernel_area);

bool_t memory_fault_handler(void *address, registers_t *regs);

void memory_cleanup(object_t *object);

#endif
